package com.wiscamp.ninechapters.panels.application.services;

import com.wiscamp.ninechapters.panels.application.assemblers.*;
import com.wiscamp.ninechapters.panels.application.command.*;
import com.wiscamp.ninechapters.panels.application.vo.*;
import com.wiscamp.ninechapters.panels.domain.entities.User;
import com.wiscamp.ninechapters.panels.domain.enums.AccountTypes;
import com.wiscamp.ninechapters.panels.domain.enums.UserTypes;
import com.wiscamp.ninechapters.panels.domain.repositories.PanelsErrorCodes;
import com.wiscamp.ninechapters.panels.domain.repositories.UserRepository;
import com.wiscamp.ninechapters.panels.domain.services.UserService;
import com.wiscamp.ninechapters.threeparties.email.EmailService;
import cube.ddd.application.BaseAppService;
import cube.ddd.application.command.DeleteCommand;
import cube.ddd.application.command.DisableCommand;
import cube.ddd.application.command.EnableCommand;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.aspect.HandleServiceException;
import cube.ddd.common.query.PagedObjects;
import cube.ddd.common.query.PagedQuery;
import cube.ddd.exception.ExceptionUtils;
import cube.ddd.utils.AppServiceUtils;
import jakarta.validation.Valid;
import org.springframework.stereotype.Service;

import java.text.MessageFormat;
import java.util.*;

@Service
public class UserAppService extends BaseAppService {

    private final UserRepository userRepository;

    private final UserService userService;

    private final EmailService emailService;

    public UserAppService(UserRepository userRepository, UserService userService, EmailService emailService) {
        this.userRepository = userRepository;
        this.userService = userService;
        this.emailService = emailService;
    }

    @HandleServiceException
    public UserVO getUser(long userId) {
        return AppServiceUtils.getById(userId, userService::getUser,
                UserAssembler.Instance::toUserVO);
    }

    @HandleServiceException
    public UserVO getUserByCode(long appId, String userCode) {
        return AppServiceUtils.getByIdAndText(appId, userCode, userRepository::getUserByCode,
                UserAssembler.Instance::toUserVO);
    }

    @HandleServiceException
    public Collection<UserVO> getUsersByName(long appId, String userName) {
        return AppServiceUtils.getListByIdAndText(appId, userName, userRepository::getUsersByName,
                UserAssembler.Instance::toUserVO);
    }

    @HandleServiceException
    public PagedObjects<UserVO> getUsersByQuery(PagedQuery pagedQuery) {
        var pagedObjects = userRepository.getUsersByQuery(pagedQuery);
        return AppServiceUtils.convertPagedObjects(pagedObjects, UserAssembler.Instance::toUserVO);
    }

    @HandleServiceException
    public Collection<MenuVO> getMenusByUser(long userId) {
        return AppServiceUtils.getListById(userId, userService::getMenusByUser,
                MenuAssembler.Instance::toMenuVO);
    }

    @HandleServiceException
    public Collection<PermissionVO> getPermissionsByUser(long roleId) {
        return AppServiceUtils.getListById(roleId, userService::getPermissionsByUser,
                PermissionAssembler.Instance::toPermissionVO);
    }

    @HandleServiceException
    public Collection<GroupVO> getGroupsByUser(long userId) {
        return AppServiceUtils.getListById(userId, userService::getGroupsByUser,
                GroupAssembler.Instance::toGroupVO);
    }

    @HandleServiceException
    public Collection<RoleVO> getRolesByUser(long userId) {
        return AppServiceUtils.getListById(userId, userService::getRolesByUser,
                RoleAssembler.Instance::toRoleVO);
    }

    @HandleServiceException
    public Collection<Long> getUserRoleIds(long userId) {
        var user = userService.getUser(userId);
        if (Objects.nonNull(user)) {
            return user.getRoleIds();
        }
        return new ArrayList<>();
    }

    @HandleServiceException
    public UserVO createUser(@Valid CreateUserCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.createOrUpdateBy(command, command.getOperatorId(), userService::createUser,
                UserAssembler.Instance::toUserVO, UserAssembler.Instance::fromCreateCommand);
    }

    @HandleServiceException
    public UserVO updateUser(@Valid UpdateUserCommand command) {
        command.setOperatorId(getCurrentUserId());

        var existUser = userService.getUser(command.getUserId());
        return AppServiceUtils.updateBy(command, existUser, command.getOperatorId(), userService::updateUser,
                UserAssembler.Instance::toUserVO, UserAssembler.Instance::mergeUpdateCommand);
    }

    @HandleServiceException
    public boolean deleteUser(@Valid DeleteCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.delete(command.getId(), command.getOperatorId(), userService::deleteUser);
    }

    @HandleServiceException
    public boolean enableUser(@Valid EnableCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.enable(command.getId(), command.getOperatorId(), userService::enableUser);
    }

    @HandleServiceException
    public boolean disableUser(@Valid DisableCommand command) {
        command.setOperatorId(getCurrentUserId());
        return AppServiceUtils.disable(command.getId(), command.getOperatorId(), userService::disableUser);
    }

    @HandleServiceException
    public boolean bindUserRole(@Valid BindUnbindUserRoleCommand command) {
        command.setOperatorId(getCurrentUserId());
        return userService.bindUserRole(command.getUserId(), command.getRoleId());
    }

    @HandleServiceException
    public boolean unbindUserRole(@Valid BindUnbindUserRoleCommand command) {
        command.setOperatorId(getCurrentUserId());
        return userService.unbindUserRole(command.getUserId(), command.getRoleId());
    }

    @HandleServiceException
    public boolean validateUserPassword(long appId, String userCode, String password) {
        var user = userRepository.getUserByCode(appId, userCode);
        if (Objects.isNull(user)) {
            return false;
        }
        // encrypt password
        return userService.validatePassword(password, user.getPassword());
    }

    @HandleServiceException
    public boolean initUserPassword(@Valid InitUserPasswordCommand command) {
        command.setOperatorId(getCurrentUserId());
        return userService.initUserPassword(command.getUserId(), command.getOperatorId());
    }

    @HandleServiceException
    public String encryptUserPassword(String originalPassword) {
        return userService.encryptUserPassword(originalPassword);
    }

    public HashMap<Long, String> getUserNames(Collection<Long> userIds) {
        var users = userRepository.getUsersByIds(userIds);
        var map = new HashMap<Long, String>();
        for (var user : users) {
            map.put(user.getUserId(), user.getUserName());
        }
        return map;
    }

    @HandleServiceException
    public UserVO signUpByEmail(SignUpByEmailCommand command) {
        var existedUser = getUserByCode(command.getApplicationId(), command.getEmail());
        if (Objects.nonNull(existedUser)) {
            ExceptionUtils.throwServiceException(PanelsErrorCodes.USER_CODE_EXISTED, command.getEmail());
        }

        var user = User.of(command.getApplicationId(), command.getEmail(), command.getUserName(),
                UserTypes.ApplicationUser, AccountTypes.LocalAccount);
        user.setEmail(command.getEmail());

        var createdUser = userService.createUser(user, command.getOperatorId());
        userService.initUserPassword(createdUser.getUserId(), command.getPassword(), command.getOperatorId());
        return getUser(createdUser.getUserId());
    }

    @HandleServiceException
    public boolean forgotPassword(ForgotPasswordCommand command) {
        var existedUser = getUserByCode(command.getApplicationId(), command.getEmail());
        if (Objects.isNull(existedUser)) {
            ExceptionUtils.throwServiceException(PanelsErrorCodes.USER_NOT_FOUND, command.getEmail());
        }

        var password = getRandomPassword();
        userService.initUserPassword(existedUser.getUserId(), password, command.getOperatorId());

        var emptyArray = new String[1];
        var receivers = new String[1];
        receivers[0] = command.getEmail();
        var contentTemplate = "Dear {0}: Your login password has been change to [ {1} ], Please use new password to login in Mathematics Contests Tutor.   -- by Nine Chapters Team";
        var content = MessageFormat.format(contentTemplate, existedUser.getUserName(), password);
        emailService.sendSimpleEmail("ninechapters2023@163.com", receivers, null, "Change Login Password", content);
        return true;
    }

    private String getRandomPassword() {
        var random = new Random().nextInt(800000);
        var password = 100000 + random;
        return password + "";
    }
}
